using System;
using System.Reflection;
using NBehave.Narrator.Framework;

namespace NBehave.Extended.BddStory
{
	public class BddStory : IBddScenario, IBddGivenFragment, IBddWhenFragment, IBddThenFragment, IBddAsAFragment, IIWantFragment, ISoThatFragment
	{
		private readonly object runningContext;
		private readonly Story underlyingStory;
		private Scenario underlyingScenario;
		private GivenFragment underlyingGivenFragment;
		private WhenFragment underlyingWhenFragment;
		private ThenFragment underlyingThenFragment;
		private string role;
		private string feature;

		public BddStory(string title, object context)
		{
			runningContext = context;
			underlyingStory = new Story(title);
		}

		public IBddScenario WithScenario(string title)
		{
			underlyingScenario = underlyingStory.WithScenario(title);
			return this;
		}

		IBddGivenFragment IBddScenario.Given(string context)
		{
			underlyingGivenFragment = underlyingScenario.Given(context, () => ProcessAction("Given", context));
			return this;
		}

		IBddGivenFragment IBddGivenFragment.And(string context)
		{
			underlyingGivenFragment.And(context, () => ProcessAction("And", context));
			return this;
		}

		IBddWhenFragment IBddGivenFragment.When(string context)
		{
			underlyingWhenFragment = underlyingGivenFragment.When(context, () => ProcessAction("When", context));
			return this;
		}

		IBddWhenFragment IBddWhenFragment.And(string context)
		{
			underlyingWhenFragment.And(context, () => ProcessAction("And", context));
			return this;
		}

		IBddThenFragment IBddWhenFragment.Then(string context)
		{
			underlyingThenFragment = underlyingWhenFragment.Then(context, () => ProcessAction("Then", context));
			return this;
		}

		IBddThenFragment IBddThenFragment.And(string context)
		{
			underlyingThenFragment.And(context, () => ProcessAction("And", context));
			return this;
		}

		private void ProcessAction(string prefix, string actionName)
		{
			var methodName = prefix + "_" + actionName.Replace(' ', '_');
			Type storyType = runningContext.GetType();
			MethodInfo method = storyType.GetMethod(methodName);
			if(method == null)
			{
				throw new Exception("Method " + methodName + " missing when processing action " + actionName);
			}
			method.Invoke(runningContext, new object[0]);
		}

		public IIWantFragment AsA(string role)
		{
			this.role = role;
			return this;
		}

		ISoThatFragment IIWantFragment.IWant(string feature)
		{
			this.feature = feature;
			return this;
		}

		BddStory ISoThatFragment.SoThat(string benefit)
		{
			underlyingStory
				.AsA(role)
				.IWant(feature)
				.SoThat(benefit);
			return this;
		}
	}
}